﻿namespace AllocineClient
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Xml.Linq;
    using MoviesDataBase;
    using System.Threading;

    /// <summary>
    /// The parser for the allocine service.
    /// </summary>
    public class AllocineParser
    {
        /// <summary>
        /// The movie search URL.
        /// </summary>
        private const string MoviesArchUrl = "http://api.allocine.fr/xml/search?q={0}&partner=1&profile=large";

        /// <summary>
        /// The suffixe of the url.
        /// </summary>
        private const string Suffixe = "&partner=1&profile=large";

        /// <summary>
        /// The found movie url.
        /// </summary>
        private const string FoundMovieUrl = "http://api.allocine.fr/xml/movie?code={0}";

        /// <summary>
        /// The base allocine URL.
        /// </summary>
        private const string BasePageUrl = "http://www.allocine.fr/film/fichefilm_gen_cfilm={0}.html";

        /// <summary>
        /// The selected movie.
        /// </summary>
        private Movie movie;

        /// <summary>
        /// Initializes a new instance of the <see cref="AllocineParser"/> class.
        /// </summary>
        /// <param name="movie">The movie.</param>
        public AllocineParser(Movie movie)
        {
            this.movie = movie;
            this.StartDownloading = new EventHandler((s, e) => { this.movie.IsDownloading = true; });
            this.DownloadEnded = new EventHandler((s, e) => { this.movie.IsDownloading = false; });
        }

        #region Events

        /// <summary>
        /// Occurs when [start downloading].
        /// </summary>
        public event EventHandler StartDownloading;

        /// <summary>
        /// Occurs when [download ended].
        /// </summary>
        public event EventHandler DownloadEnded;

        #endregion

        /// <summary>
        /// Searches the movie.
        /// </summary>
        public void DownloadMovieInformationAsync()
        {
            ThreadStart ts = new ThreadStart(() =>
            {
                try
                {

                    string title = RemoveDiacritics(this.movie.Title);
                    WebClient client = new WebClient();
                    client.Encoding = Encoding.UTF8;
                    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.ParseSearchResult);
                    client.DownloadStringAsync(new Uri(string.Format(MoviesArchUrl, title.Replace(" ", "+").Replace("'", " "))));
                    this.StartDownloading.Invoke(this, EventArgs.Empty);
                }
                catch
                {
                    throw;
                }
            });

            new Thread(ts).Start();
        }

        /// <summary>
        /// Updates the data from URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        public void UpdateDataFromUrl(string url)
        {
            string id = url.Split('=')[1].Split('.')[0];
            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.RetrieveMovieInformations);
            client.DownloadStringAsync(new Uri(string.Format(FoundMovieUrl, id) + Suffixe), this.movie);
        }

        /// <summary>
        /// Removes the diacritics.
        /// </summary>
        /// <param name="inputString">The input string.</param>
        /// <returns>The converted string.</returns>
        private static string RemoveDiacritics(string inputString)
        {
            string normalizedString = inputString.Normalize(NormalizationForm.FormD);
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < normalizedString.Length; i++)
            {
                char c = normalizedString[i];
                if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
                {
                    stringBuilder.Append(c);
                }
            }

            return stringBuilder.ToString();
        }

        /// <summary>
        /// Parses the search result.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Net.DownloadStringCompletedEventArgs"/> instance containing the event data.</param>
        private void ParseSearchResult(object sender, DownloadStringCompletedEventArgs e)
        {
            try
            {
                string search = e.Result.Replace("atom:", string.Empty);
                XDocument document = XDocument.Parse(search);

                var results = from feed in document.Descendants("feed")
                              from entry in feed.Descendants("entry")
                              select entry.Element("id").Value;

                if (results.Count() > 0)
                {
                    WebClient client = new WebClient();
                    client.Encoding = Encoding.UTF8;
                    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.RetrieveMovieInformations);
                    string movieId = results.ElementAt(0).Split('=')[1];
                    this.movie.AllocineUrl = string.Format(BasePageUrl, movieId);
                    client.DownloadStringAsync(new Uri(results.ElementAt(0) + Suffixe), this.movie);
                }
                else
                {
                    this.DownloadEnded.Invoke(sender, e);
                }
            }
            catch
            {
                this.DownloadEnded.Invoke(sender, e);
            }
        }

        /// <summary>
        /// Retrieves the movie informations.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Net.DownloadStringCompletedEventArgs"/> instance containing the event data.</param>
        private void RetrieveMovieInformations(object sender, DownloadStringCompletedEventArgs e)
        {
            try
            {
                Movie downloadedMovieInformations = (Movie)e.UserState;
                XDocument document = XDocument.Parse(e.Result);
                IEnumerable<XElement> elements = document.Descendants();

                XElement keywords = elements.First(d => d.Name.LocalName == "keywords");
                if (!keywords.Value.Contains(this.movie.Title))
                {
                    this.CleanMovie();
                    throw new Exception("Le film n'a pas été retrouvé.");
                }

                IEnumerable<XElement> nationalities = elements.Where(d => d.Name.LocalName == "nationality");
                IEnumerable<XElement> genres = elements.Where(d => d.Name.LocalName == "genre");
                XElement dateSortie = elements.FirstOrDefault(d => d.Name.LocalName == "releaseDate");
                XElement synopsis = elements.FirstOrDefault(d => d.Name.LocalName == "synopsis");

                downloadedMovieInformations.DateSortie = dateSortie == null ? string.Empty : dateSortie.Value;
                downloadedMovieInformations.Synopsis = synopsis == null ? string.Empty : synopsis.Value.Replace("<br/>", "\r\n");

                foreach (XElement nationality in nationalities)
                {
                    downloadedMovieInformations.Nationalite = nationality.Value + (nationalities.Last() == nationality ? string.Empty : ", ");
                }

                foreach (XElement genre in genres)
                {
                    downloadedMovieInformations.Genre += genre.Value + (genres.Last() == genre ? string.Empty : ", ");
                }

                var poster = document.Descendants().Where(d => d.Name.LocalName == "poster").FirstOrDefault();
                if (poster != null)
                {
                    this.movie.Poster = poster.Attribute("href").Value;
                }

                this.movie.CastMembers.Clear();
                XElement element = elements.FirstOrDefault(d => d.Name.LocalName == "casting");
                foreach (XElement xe in element.Elements())
                {
                    IEnumerable<XElement> descendants = xe.Descendants();
                    string activity = descendants.FirstOrDefault(d => d.Name.LocalName == "activity").Value;
                    string person = descendants.FirstOrDefault(d => d.Name.LocalName == "person").Value;
                    XElement role = descendants.FirstOrDefault(d => d.Name.LocalName == "role");
                    XElement picture = descendants.FirstOrDefault(d => d.Name.LocalName == "picture");

                    CastMember cm = new CastMember()
                    {
                        Activity = activity,
                        Person = person,
                        Role = role == null ? string.Empty : role.Value,
                        Picture = picture == null ? string.Empty : picture.Attribute("href").Value
                    };

                    downloadedMovieInformations.CastMembers.Add(cm);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
                this.CleanMovie();
            }

            this.DownloadEnded.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Cleans the movie.
        /// </summary>
        private void CleanMovie()
        {
            this.movie.Poster = null;
            this.movie.Synopsis = string.Empty;
            this.movie.Nationalite = string.Empty;
            this.movie.CastMembers = null;
            this.movie.DateSortie = string.Empty;
            this.movie.Genre = string.Empty;
            this.movie.AllocineUrl = string.Empty;
        }
    }
}
